﻿using System;
using System.Collections.Generic;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.Common;

namespace Fuse8.DomainFramework.CacheService
{
    /// <summary>
    /// Indicates the mode how the items are stored
    /// </summary>     
    public enum CacheStoreMode
    {
        /// <summary>
        /// Store the data, but only if the server does not already hold data for a given key
        /// </summary>     
        Add = 0,
        /// <summary>
        /// Store the data, but only if the server does already hold data for a given key
        /// </summary>
        Replace = 1,
        /// <summary>
        /// Store the data, overwrite if already exist
        /// </summary>    
        Set = 2,
    }

    /// <summary>
    /// Base realization of entity cache service
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    public abstract class DefaultCacheService<TEntity, TKey> : IEntityCacheService<TEntity, TKey>
        where TEntity : class, IEntityObject<TKey>, new()
        where TKey : IComparable<TKey>, IEquatable<TKey> 
    {
        #region Fields


        private IEntityCacheService<TEntity, TKey> _successor;

        private static readonly string _typeParameter = new TEntity().TypeParameter;

        /// <summary>
        /// 
        /// </summary>
        protected static readonly string LockedOnAddValue = "locked";

        /// <summary>
        /// Delegate for add item to cache
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="cacheKey"></param>
        /// <param name="cacheitem"></param>
        /// <returns></returns>
        protected delegate bool AsyncAddToCache(CacheStoreMode mode, string cacheKey, EntityCacheItem<TEntity> cacheitem);

        /// <summary>
        /// Delegate for add dependency to cache
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="cacheKey"></param>
        /// <param name="dependency"></param>
        /// <param name="absoluteExpiration"></param>
        /// <param name="slidingExpiration"></param>
        /// <returns></returns>
        protected delegate bool AsyncAddDependencyToCache(CacheStoreMode mode, string cacheKey, EntityCacheItemDependency dependency, DateTime absoluteExpiration, TimeSpan slidingExpiration);

        /// <summary>
        /// Delegate for remove item from cache
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <returns></returns>
        protected delegate bool AsyncRemoveFromCache(string cacheKey);

        #endregion

        #region Properties

        /// <summary>
        /// Entity's type parameter. Used in cache key generation
        /// </summary>
        protected string TypeParameter
        {
            get
            {
                return _typeParameter;
            }
        }

        /// <summary>
        /// Successor in chain
        /// </summary>
        protected internal IEntityCacheService<TEntity, TKey> Successor
        {
            get
            {
                return _successor;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public CacheServiceOptions CacheOptions { get; set; }

        #endregion

        #region IEntityCacheService<T> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        /// <param name="cacheKey"></param>
        public virtual void AddItemToCache(string cacheKey, TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");
            Guard.ArgumentNotEmpty(cacheKey, "cacheKey");

            DirectAddItemToCache(cacheKey, entityObject);

            if (Successor != null)
            {
                Successor.AddItemToCache(cacheKey, entityObject);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <param name="entityObjects"></param>
        public virtual void AddListToCache(string cacheKey, IList<TEntity> entityObjects)
        {
            Guard.ArgumentNotNull(entityObjects, "entityObject");
            Guard.ArgumentNotEmpty(cacheKey, "cacheKey");

            DirectAddListToCache(cacheKey, entityObjects);

            if (Successor != null)
            {
                Successor.AddListToCache(cacheKey, entityObjects);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <returns></returns>
        public virtual TEntity GetFromCache(string cacheKey)
        {
            Guard.ArgumentNotEmpty(cacheKey, "cacheKey");

            TEntity result = null;

            if (Successor != null)
            {
                result = Successor.GetFromCache(cacheKey);
            }

            if (result == null)
            {
                result = DirectGetFromCache(cacheKey);

                if (result != null)
                {
                    if (Successor != null)
                    {
                        Successor.AddItemToCache(cacheKey, result);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <returns></returns>
        public virtual IList<TEntity> GetListFromCache(string cacheKey)
        {
            Guard.ArgumentNotEmpty(cacheKey, "cacheKey");

            IList<TEntity> result = null;

            if (Successor != null)
            {
                result = Successor.GetListFromCache(cacheKey);
            }

            if (result == null)
            {
                result = DirectGetListFromCache(cacheKey);

                if (result != null)
                {
                    if (Successor != null)
                    {
                        Successor.AddListToCache(cacheKey, result);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheKey"></param>
        public virtual void RemoveItemFromCache(string cacheKey)
        {
            Guard.ArgumentNotEmpty(cacheKey, "cacheKey");

            DirectRemoveItemFromCache(cacheKey);

            if (Successor != null)
            {
                Successor.RemoveItemFromCache(cacheKey);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void ClearCache()
        {
            if (Successor != null)
            {
                Successor.ClearCache();
            }

            DirectClearCache();
        }

        /// <summary>
        /// Add cache key to dependency list
        /// </summary>
        /// <param name="cacheKey"></param>
        public virtual void AddToDependency(string cacheKey)
        {
            if (Successor != null)
            {
                Successor.AddToDependency(cacheKey);
            }
        }

        /// <summary>
        /// Clear all dependencies
        /// </summary>
        public virtual void ClearDependency(TEntity entityObject)
        {
            if (Successor != null)
            {
                Successor.ClearDependency(entityObject);
            }
        }

        /// <summary>
        /// Clear all dependencies
        /// </summary>
        public virtual void ClearDependency(TKey id)
        {
            if (Successor != null)
            {
                Successor.ClearDependency(id);
            }
        }
		
        /// <summary>
        /// 
        /// </summary>
        /// <param name="successor"></param>
        public virtual void SetSuccessor(IEntityCacheService<TEntity, TKey> successor)
        {
            _successor = successor;
        }        

        #endregion

        #region Abstraction

        /// <summary>
        /// Add item to concrete cache
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <param name="entityObject"></param>
        protected abstract void DirectAddItemToCache(string cacheKey, TEntity entityObject);

        /// <summary>
        /// Add list to concrete cache
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <param name="entityObjects"></param>
        protected abstract void DirectAddListToCache(string cacheKey, IList<TEntity> entityObjects);

        /// <summary>
        /// Get from concrete cache
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <returns></returns>
        protected abstract TEntity DirectGetFromCache(string cacheKey);

        /// <summary>
        /// Get from concrete cache
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <returns></returns>
        protected abstract IList<TEntity> DirectGetListFromCache(string cacheKey);

        /// <summary>
        /// Remove from concrete cache
        /// </summary>
        /// <param name="cacheKey"></param>
        protected abstract void DirectRemoveItemFromCache(string cacheKey);

        /// <summary>
        /// Clear concrete cache
        /// </summary>
        protected abstract void DirectClearCache();

        /// <summary>
        /// 
        /// </summary>
        public abstract IEnumerable<CacheItemView> CacheItems { get; }

        #endregion
    }
}
